package jeff.plugin.mybatis.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.w3c.dom.Attr;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

/**
 * w3c中的node操作
 * @author Jeff
 */
public class DOMUtils {

	/**
	 * 获取element节点的属性
	 * @param el
	 * @param attrName
	 * @return
	 */
	public static String getAttribute(Element element, String attrName) {
		Assert.notNull(element, "Element is not null");
		Assert.notNull(attrName, "attrName is not null");
		String sRet = null;
		Attr attr = element.getAttributeNode(attrName);
		
		if (attr != null) {
			sRet = attr.getValue();
		}
		return sRet;
	}
	
	/**
	 * element节点中是否含有element节点
	 * @param element
	 * @return
	 */
	public static boolean hasElement(Element element){
		Assert.notNull(element, "Element is not null");
		for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.ELEMENT_NODE) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 获取element节点中的第一个element节点
	 * @param elem
	 * @return
	 */
	public static Element getFirstChildElement(Element element) {
		Assert.notNull(element, "Element is not null");
		for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) n;
			}
		}
		return null;
	}
	
	/**
	 * 获取同层级element节点的下一个element节点
	 * @param element
	 * @return
	 */
	public static Element getNextSiblingElement(Element element) {
		Assert.notNull(element, "Element is not null");
		for (Node n = element.getNextSibling(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) n;
			}
		}
		return null;
	}
	
	/**
	 * element中是否含有文本节点
	 * @param elem
	 * @return
	 */
	public static boolean hasText(Element element){
		Assert.notNull(element, "Element is not null");
		for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.TEXT_NODE) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 元素中是否含有唯一的text节点
	 * @param element
	 * @return
	 */
	public static boolean hasOnlyText(Element element){
		Assert.notNull(element, "Element is not null");
		NodeList nodelist = element.getChildNodes();
		if(nodelist.getLength() != 1){
			return false;
		}
		Node n = element.getFirstChild();
		return n.getNodeType() == Node.TEXT_NODE;
	}
	
	/**
	 * 获取element中第一个文本节点
	 * @param element
	 * @return
	 */
	public static Text getFirstChildText(Element element) {
		Assert.notNull(element, "Element is not null");
		for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.TEXT_NODE) {
				return (Text) n;
			}
		}
		return null;
	}
	
	/**
	 * element节点下是否是一个数组节点
	 * @param element
	 * @return
	 */
	public static boolean isArrayElement(Element element){
		Assert.notNull(element, "Element is not null");
		if(element.getChildNodes().getLength() == 0){
			return false;
		}
		
		String localName = null;
		for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
			if (n.getNodeType() == Node.ELEMENT_NODE) {
				String name = n.getLocalName();
				if(localName == null){
					localName = name;
					continue;
				}
				if(!localName.equals(name)){
					return false;
				}
			}
		}
		return localName!=null;
	}
	
	/**
	 * element中是否是一个对象element
	 * @param element
	 * @return
	 */
	public static boolean isObjectElement(Element element){
		Assert.notNull(element, "element must not be null");
		if(element == null || element.getChildNodes().getLength() == 0){
			return false;
		}
		return isArrayElement(element);
	}
	
	/**
	 * 节点名是否匹配
	 * @param node
	 * @param desiredName
	 * @return
	 */
	public static boolean nodeNameEquals(Node node, String desiredName) {
		Assert.notNull(node, "Node must not be null");
		Assert.notNull(desiredName, "Desired name must not be null");
		return nodeNameMatch(node, desiredName);
	}
	
	/**
	 * 获取element中的text
	 * @param valueEle
	 * @return
	 */
	public static String getTextValue(Element valueEle) {
		Assert.notNull(valueEle, "Element must not be null");
		StringBuilder sb = new StringBuilder();
		NodeList nl = valueEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node item = nl.item(i);
			if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
				sb.append(item.getNodeValue());
			}
		}
		return sb.toString();
	}
	
	/**
	 * 获取element中的子element
	 * @param ele
	 * @return
	 */
	public static List<Element> getChildElements(Element ele) {
		Assert.notNull(ele, "Element must not be null");
		NodeList nl = ele.getChildNodes();
		List<Element> childEles = new ArrayList<Element>();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				childEles.add((Element) node);
			}
		}
		return childEles;
	}
	
	/**
	 * 获取element节点中指定名称的element节点
	 * @param ele
	 * @param childEleName
	 * @return
	 */
	public static Element getChildElementByTagName(Element ele, String childEleName) {
		Assert.notNull(ele, "Element must not be null");
		Assert.notNull(childEleName, "Element name must not be null");
		NodeList nl = ele.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element && nodeNameMatch(node, childEleName)) {
				return (Element) node;
			}
		}
		return null;
	}
	
	/**
	 * 获取element节点中指定名称的所有节点
	 * @param ele
	 * @param childEleName
	 * @return
	 */
	public static List<Element> getChildElementsByTagName(Element ele, String childEleName) {
		return getChildElementsByTagName(ele, new String[] {childEleName});
	}
	
	/**
	 *获取element节点中指定名称的所有节点
	 * @param ele
	 * @param childEleName
	 * @return
	 */
	public static List<Element> getChildElementsByTagName(Element ele, String... childEleNames) {
		Assert.notNull(ele, "Element must not be null");
		Assert.notNull(childEleNames, "Element names collection must not be null");
		List<String> childEleNameList = Arrays.asList(childEleNames);
		NodeList nl = ele.getChildNodes();
		List<Element> childEles = new ArrayList<Element>();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
				childEles.add((Element) node);
			}
		}
		return childEles;
	}
	
	/**
	 * 获取element节点中指定节点的值
	 * @param ele
	 * @param childEleName
	 * @return
	 */
	public static String getChildElementValueByTagName(Element ele, String childEleName) {
		Element child = getChildElementByTagName(ele, childEleName);
		return (child != null ? getTextValue(child) : null);
	}
	
	
	//---------------------------private---------------------------------------------
	
	/**
	 * Matches the given node's name and local name against the given desired name.
	 */
	private static boolean nodeNameMatch(Node node, String desiredName) {
		return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
	}

	/**
	 * Matches the given node's name and local name against the given desired names.
	 */
	private static boolean nodeNameMatch(Node node, Collection<?> desiredNames) {
		return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
	}
	
}
